package baseFx.database.jdbc;

import baseFx.common.Assert;
import baseFx.common.utils.EnumUtils;
import baseFx.common.utils.ExceptionUtils;
import baseFx.database.*;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

public abstract class AbstractJDBCSql extends AbstractSql {
    private Connection connection;
    private DataSource dataSource;
    private List<Connection> cursorConnList = new ArrayList<>();

    public AbstractJDBCSql(DataSource dataSource) {
        this.dataSource = dataSource;
        eventBefore().add((s, e) -> {
            replaceParamName(e.param);
            changeEnumValue(e.param);
        });
    }

    private Connection getConnection() {
        if (connection != null) {
            return connection;
        }
        return getNewConnection();
    }

    private Connection getNewConnection() {
        try {
            Connection conn = dataSource.getConnection();
            conn.setAutoCommit(true);
            return conn;
        } catch (SQLException e) {
            throw ExceptionUtils.toRuntime(e);
        }
    }

    protected abstract PreparedStatement createStatement(Connection connection, SqlParam param, int fetchSize);

    private void changeEnumValue(SqlParam param) {
        if (param.args.size() > 0) {
            for (String k : param.args.keySet()) {
                if (param.args.get(k) instanceof Enum) {
                    param.args.put(k, processEnum(k, (Enum<?>) param.args.get(k)));
                }
            }
        }
    }

    protected Object processEnum(String name, Enum<?> e) {
        Object value = EnumUtils.getId(e);
        return value != null ? value : e.name();
    }

    private ResultSet exec(PreparedStatement statement) {
        try {
            return statement.executeQuery();
        } catch (SQLException e) {
            throw ExceptionUtils.toRuntime(e);
        }
    }

    @Override
    public final String paramName(String name) {
        return "#" + name;
    }

    private void replaceParamName(SqlParam param) {
        if (param.args.isEmpty()) {
            return;
        }
        int offset = 0;
        Map<Integer, String> posMap = new HashMap<>();
        List<Integer> posList = new ArrayList<>();
        Map<String, Object> args = new LinkedHashMap<>();
        for (String name : param.args.keySet()) {
            int start = 0;
            while (true) {
                int idx = param.command.indexOf(paramName(name), start);
                if (idx < 0) {
                    break;
                }
                start = idx + 1;
                posMap.put(idx, name);
                posList.add(idx);
            }
        }
        Collections.sort(posList);
        String newSql = param.command;
        for (Integer idx : posList) {
            String name = posMap.get(idx);
            if (param.args.containsKey(name)) {
                String newName = name + "_" + args.size();
                String holder = placeholder(newName);
                newSql = newSql.replaceFirst(paramName(name), holder);
                args.put(newName, param.args.get(name));
            } else {
                throw new RuntimeException("缺少参数值:" + name);
            }
        }
        param.args.clear();
        param.args.putAll(args);
        param.command = newSql;
    }

    @Override
    public void changeDataSource(String key) {
        Assert.isFalse(inTransaction(), "开启事务时不能切换数据源");
        Assert.isTrue(dataSource instanceof MultiDataSource, "dataSource的类型不是" + MultiDataSource.class.getCanonicalName());
        ((MultiDataSource) dataSource).set(key);
    }

    @Override
    public String placeholder(String name) {
        return "?";
    }

    @Override
    protected int onExecute(SqlParam param) {
        Connection connection = getConnection();
        PreparedStatement statement = createStatement(connection, param, 0);
        try {
            return statement.executeUpdate();
        } catch (SQLException ex) {
            throw ExceptionUtils.toRuntime(ex);
        } finally {
            if (inTransaction() == false) {
                try {
                    statement.close();
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    protected <T> List<T> onQuery(Class<T> clazz, SqlParam param) {
        Connection connection = getConnection();
        PreparedStatement statement = createStatement(connection, param, 0);
        try {
            try (ResultSet rs = exec(statement)) {
                List<Map<?, ?>> data = Utils.resultSetToList(rs);
                return param.convert(clazz, data);
            }
        } catch (SQLException ex) {
            throw ExceptionUtils.toRuntime(ex);
        } finally {
            if (inTransaction() == false) {
                try {
                    statement.close();
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    protected MultipleReader onMultiple(SqlParam param) {
        Connection connection = getConnection();
        PreparedStatement statement = createStatement(connection, param, 0);
        return new JDBCMultipleReader(statement, param) {
            @Override
            public void close() {
                super.close();
                if (inTransaction() == false) {
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
    }

    @Override
    protected <T> CursorReader<T> onCursor(Class<T> clazz, SqlParam param, int fetchSize, boolean independent) {
        Connection conn = independent ? getNewConnection() : getConnection();
        PreparedStatement statement = createStatement(conn, param, fetchSize);
        ResultSet rs = exec(statement);
        if (independent) {
            cursorConnList.add(conn);
        }
        return new JDBCCursorReader<T>(clazz, param, rs) {
            private boolean ind = independent;
            private Connection curConn = conn;

            @Override
            public void close() {
                if (ind || inTransaction() == false) {
                    try {
                        super.close();
                        curConn.close();
                        cursorConnList.remove(curConn);
                    } catch (SQLException e) {
                        throw ExceptionUtils.toRuntime(e);
                    }
                }
            }
        };
    }

    @Override
    protected void beginTransaction(TransactionLevel level) {
        Assert.isNull(connection, new RuntimeException("不能重复开启事务。"));
        try {
            connection = dataSource.getConnection();
            connection.setAutoCommit(false);
            switch (level) {
                case ReadUncommitted:
                    connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
                    break;
                case ReadCommitted:
                    connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
                    break;
                case RepeatableRead:
                case Default:
                    connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
                    break;
                case Serializable:
                    connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
                    break;
                default:
                    throw new RuntimeException("未实现的事务等级：" + level.name());
            }
        } catch (SQLException e) {
            throw ExceptionUtils.toRuntime(e);
        }
    }

    @Override
    protected void commitTransaction() {
        Assert.isNotNull(connection, new RuntimeException("事务未开启。"));
        try {
            connection.commit();
            connection.close();
            connection = null;
        } catch (SQLException e) {
            throw ExceptionUtils.toRuntime(e);
        }
    }

    @Override
    protected void rollbackTransaction() {
        Assert.isNotNull(connection, new RuntimeException("事务未开启。"));
        try {
            connection.rollback();
            connection.close();
            connection = null;
        } catch (SQLException e) {
            throw ExceptionUtils.toRuntime(e);
        }
    }

    @Override
    public boolean isClosed() {
        return dataSource == null;
    }

    @Override
    public void close() {
        super.close();
        if (connection != null) {
            try {
                connection.close();
                connection = null;
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (cursorConnList != null) {
            for (Connection conn : cursorConnList) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            cursorConnList.clear();
            cursorConnList = null;
        }
        dataSource = null;
    }
}
